package com.zx.core.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 雪花算法ID生成器
 */
public class SnowflakeIdUtils {
    private static final Logger logger = LoggerFactory.getLogger(SnowflakeIdUtils.class);

    // 起始时间戳 (2025-01-01 00:00:00)
    // 建议使用项目开始时间，这样可以延长可用年限
    private final static long START_STMP = 1735660800000L;

    // 各部分位数
    // 序列号占用的位数
    private final static long SEQUENCE_BIT = 12;
    // 机器标识占用的位数
    private final static long MACHINE_BIT = 5;
    // 数据中心占用的位数
    private final static long DATACENTER_BIT = 5;

    // 最大值
    private final static long MAX_DATACENTER_NUM = ~(-1L << DATACENTER_BIT);
    private final static long MAX_MACHINE_NUM = ~(-1L << MACHINE_BIT);
    private final static long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);

    // 位移量
    private final static long MACHINE_LEFT = SEQUENCE_BIT;
    private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
    private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;

    private final long datacenterId;
    private final long machineId;
    private long sequence = 0L;
    private long lastStmp = -1L;

    /**
     * 构造函数
     */
    public SnowflakeIdUtils(long datacenterId, long machineId) {
        if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
            throw new IllegalArgumentException(
                    String.format("datacenter Id can't be greater than %d or less than 0", MAX_DATACENTER_NUM));
        }
        if (machineId > MAX_MACHINE_NUM || machineId < 0) {
            throw new IllegalArgumentException(
                    String.format("machine Id can't be greater than %d or less than 0", MAX_MACHINE_NUM));
        }
        this.datacenterId = datacenterId;
        this.machineId = machineId;

        logger.info("SnowflakeIdUtils initialized with datacenterId: {}, machineId: {}", datacenterId, machineId);
    }

    /**
     * 解析ID的各个部分（用于调试）
     */
    public static void parseId(long id) {
        long sequence = id & MAX_SEQUENCE;
        long machineId = (id >> MACHINE_LEFT) & MAX_MACHINE_NUM;
        long datacenterId = (id >> DATACENTER_LEFT) & MAX_DATACENTER_NUM;
        long timestamp = (id >> TIMESTMP_LEFT) + START_STMP;

        System.out.println("Snowflake ID解析:");
        System.out.println("ID: " + id);
        System.out.println("时间戳: " + timestamp + " (" + new java.util.Date(timestamp) + ")");
        System.out.println("数据中心ID: " + datacenterId);
        System.out.println("机器ID: " + machineId);
        System.out.println("序列号: " + sequence);
    }

    /**
     * 生成下一个ID
     */
    public synchronized long nextId() {
        long currStmp = getNewstmp();

        // 处理时钟回拨
        if (currStmp < lastStmp) {
            handleClockBackwards(currStmp);
            currStmp = getNewstmp(); // 重新获取时间戳
        }

        if (currStmp == lastStmp) {
            sequence = (sequence + 1) & MAX_SEQUENCE;
            if (sequence == 0L) {
                currStmp = getNextMill();
            }
        } else {
            sequence = 0L;
        }

        lastStmp = currStmp;

        return ((currStmp - START_STMP) << TIMESTMP_LEFT)
                | (datacenterId << DATACENTER_LEFT)
                | (machineId << MACHINE_LEFT)
                | sequence;
    }

    /**
     * 处理时钟回拨
     */
    private void handleClockBackwards(long currStmp) {
        long offset = lastStmp - currStmp;

        // 小范围回拨（5毫秒内），等待时钟追上来
        if (offset <= 5) {
            try {
                logger.warn("Clock moved backwards {} milliseconds, waiting...", offset);
                Thread.sleep(offset << 1);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RuntimeException("Thread interrupted while handling clock move backwards", e);
            }
        } else {
            // 大范围回拨，抛出异常
            throw new RuntimeException(
                    String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", offset));
        }
    }

    private long getNextMill() {
        long mill = getNewstmp();
        while (mill <= lastStmp) {
            mill = getNewstmp();
        }
        return mill;
    }

    private long getNewstmp() {
        return System.currentTimeMillis();
    }
}